/**
 * Jaxson ajax
 *
 * Copyright Remedy Interactive, licensed under Apache version 2 license:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0.html
 *
 * This notice must remain in all java source files.
 */

package org.jaxson.struts.reverseAjax;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jaxson.struts.BaseAction;
import org.jaxson.struts.encoder.EncodingException;
import org.jaxson.struts.encoder.JsonEncoder;
import org.jaxson.util.servlet.ServletContextEventDispatcher;


/**
 * Reverse Ajax Stream, currenlty only supports FF.
 * 
 * @author Joe Maisel
 */
public class StreamAction extends BaseAction implements ServletContextListener
{
	private static final long serialVersionUID = 5432234512347351L;
	private static final Log lager = LogFactory.getLog( StreamAction.class.getName() );

	private StreamThread thread;
	private static final String SESSION_KEY = StreamAction.class.getName();

	private class StreamThread extends Thread
	{
		private ServletOutputStream stream;
		private HttpServletResponse response;
		private HttpServletRequest request;
		private boolean doRun = true;

		public void run()
		{
			String sid = request.getSession().getId();
			try
			{
				stream.println();
				response.flushBuffer();
				lager.debug( "reverse ajax stream opened" );

				while( doRun )
				{
					// if( response.isCommitted() )
					// break;

					HttpSession session = request.getSession();

					ReverseAjaxBatch batch = (ReverseAjaxBatch) session
							.getAttribute( ReverseAjaxManager.BATCHED_SESSION_KEY );

					ReverseAjaxBatch asap = (ReverseAjaxBatch) session
							.getAttribute( ReverseAjaxManager.IMMEDIATE_SESSION_KEY );

					if( batch != null && batch.isCommitted() )
					{
						lager.debug( "sending batched events" );
						ReverseAjaxManager.clearBatch( request.getSession() );
						JsonEncoder enc = new JsonEncoder();
						enc.setOutputStream( stream );
						enc.encode( batch );
						stream.println();
						response.flushBuffer();
					}

					if( asap != null && asap.getEvents().size() > 0 )
					{
						lager.debug( "sending immediate events" );
						ReverseAjaxManager.clearBatch( request.getSession() );
						JsonEncoder enc = new JsonEncoder();
						enc.setOutputStream( stream );
						enc.encode( asap );
						session.setAttribute( ReverseAjaxManager.IMMEDIATE_SESSION_KEY, new ReverseAjaxBatch() );
						stream.println();
						response.flushBuffer();
					}

					sleep( 1000 );
				}

				lager.debug( "thread terminated cleanly for " + sid );
			}
			catch( InterruptedException e )
			{
				e.printStackTrace();
			}
			catch( IOException e )
			{
				String className = e.getClass().getName();

				if( className.contains( "ClientAbortException" ) ) // tomcat
																	// specific.
				{
					lager.debug( "closing reverse ajax stream for " + sid );
				}
				else
				{
					e.printStackTrace();
				}
				doRun = false;
			}
			catch( EncodingException e )
			{
				e.printStackTrace();
			}
			catch( Exception e )
			{
				e.printStackTrace();
			}
		}

	};

	@Override
	public String handle()
	{
		// if( response.isCommitted() )
		// return null;

		setReturnAs( "json" );
		ServletContextEventDispatcher.subscribe( this );
		session.setAttribute( "reverse.ajax.method", "stream" );
		session.setAttribute( "reverse.ajax.stream", this );

		try
		{
			// if the user reloads the page, kill the old
			// stream action
			if( session.getAttribute( SESSION_KEY ) != null )
			{
				lager.debug( "found old StreamAction" );
				StreamAction old = (StreamAction) session.getAttribute( SESSION_KEY );
				old.abort();
			}

			session.setAttribute( SESSION_KEY, this );

			response.setHeader( "Transfer-Encoding", "chunked" );
			thread = new StreamThread();
			thread.stream = response.getOutputStream();
			thread.response = response;
			thread.request = request;
			thread.start();
			thread.join();
		}
		catch( IOException e )
		{
			lager.debug( "reverse ajax stream closed for " + request.getSession().getId() );
			e.printStackTrace();

		}
		catch( InterruptedException e )
		{
			e.printStackTrace();
		}

		ServletContextEventDispatcher.unsubscribe( this );

		return null;
	}

	/**
	 * Cleanly exits stream thread.
	 */
	public void abort()
	{
		thread.doRun = false;
	}

	/**
	 * Cleanly exits stream thread.
	 */
	@Override
	public void contextDestroyed( ServletContextEvent sce )
	{
		abort();
	}

	/**
	 * Unimplemented: API Compatibility.
	 */
	@Override
	public void contextInitialized( ServletContextEvent sce )
	{

	}

}
